package com.shockweb.rpc;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 熔断设置
 * @author pengminghua
 *
 */
public class Fuse {
	
	
	/**
	 * 构造方法
	 * @param fuseCycleTime
	 * @param fuseCycleTimeNum
	 * @param fuseWaitInterval
	 * @param fuseErrorThreshold
	 * @param fuseErrorPercentage
	 */
	public Fuse(long fuseCycleTime,int fuseCycleTimeNum,long fuseWaitInterval,long fuseErrorThreshold,int fuseErrorPercentage) {
    	if(fuseCycleTime>0 && fuseCycleTimeNum>0) {
        	long len = fuseCycleTime/fuseCycleTimeNum;
        	if(len==0) {
        		len=1;
        	}
        	int temp = 0;
        	for(int i=0;i<fuseCycleTimeNum;i++) {
        		if(len*i>=fuseCycleTime) {
        			temp ++;
        			break;
        		}else{
        			temp ++;
        		}
        	}
        	fuseCycleTimeNum = temp;
            try {
            	invokeLock.writeLock().lock();
            	invokeCounts = new long[fuseCycleTimeNum];
            	invokeErrorCounts = new long[fuseCycleTimeNum];
            } finally {
            	invokeLock.writeLock().unlock();
            }
        }
        this.fuseCycleTime = fuseCycleTime;
        this.fuseCycleTimeNum = fuseCycleTimeNum;
        this.fuseWaitInterval = fuseWaitInterval;
        this.fuseErrorThreshold = fuseErrorThreshold;
        this.fuseErrorPercentage = fuseErrorPercentage;
	}

	   /**
     * 熔断统计调用失败的时间周期，毫秒
     */
    private long fuseCycleTime = 5*1000;
    


    
    /**
     * 熔断统计调用失败的时间周期被划分的数量
     */
    private int fuseCycleTimeNum = 10;
    
    
    /**
     * 熔断发生后再次重试时间间隔，毫秒
     */
    private long fuseWaitInterval = 10*1000;
    

    
    /**
     * 熔断触发限额次数
     */
    private long fuseErrorThreshold = -1;
    
    
    /**
     * 熔断触发限额错误百分比率
     */
    private int fuseErrorPercentage = -1;
    
    
    private final ReadWriteLock invokeLock = new ReentrantReadWriteLock();

    /**
     * 统计调用次数
     */
    private volatile long[] invokeCounts = null;
    /**
     * 最后统计时间
     */
    private volatile long fuseCountTime = 0;
    /**
     * 统计调用出错次数
     */
    private volatile long[] invokeErrorCounts = null;
    /**
     * 最后统计错误时间
     */
    private volatile long fuseErrorCountTime = 0;
    /**
     * 熔断发生时间
     */
    private volatile long fuseTime = 0;
    

    /**
     * 熔断
     * @return
     */
    public boolean isFuse() {
    	if(fuseCycleTime>0 && fuseCycleTimeNum>0 && (fuseErrorThreshold>=0 || fuseErrorPercentage>=0)) {
        	if(fuseTime + fuseWaitInterval>System.currentTimeMillis()) {
        		return true;
        	}else {
            	long len = fuseCycleTime/fuseCycleTimeNum;
            	if(len==0) {
            		len=1;
            	}
            	int timeNum = (int)((System.currentTimeMillis()-System.currentTimeMillis()%len)/len - fuseCountTime/len);
            	fuseCountTime = (System.currentTimeMillis()-System.currentTimeMillis()%len);

            	int timeErrorNum = (int)((System.currentTimeMillis()-System.currentTimeMillis()%len)/len - fuseErrorCountTime/len);
            	fuseErrorCountTime = (System.currentTimeMillis()-System.currentTimeMillis()%len);
	            try {
	            	invokeLock.readLock().lock();
                	if(timeErrorNum>0){
                    	for(int i=0;i<invokeErrorCounts.length;i++) {
                    		if(i+timeErrorNum>=invokeErrorCounts.length) {
                    			invokeErrorCounts[i] = 0;
                    		}else {
                    			invokeErrorCounts[i] = invokeErrorCounts[i+timeErrorNum];
                    		}
                    	}
                	}
                	if(timeNum>0){
                    	for(int i=0;i<invokeCounts.length;i++) {
                    		if(i+timeNum>=invokeCounts.length) {
                    			invokeCounts[i] = 0;
                    		}else {
                    			invokeCounts[i] = invokeCounts[i+timeNum];
                    		}
                    	}
                	}
            		int size = invokeErrorCounts.length;
        			long errorsSum = 0;
        			for(int i=0;i<size;i++) {
        				errorsSum += invokeErrorCounts[i];
        			}
        			long sum = 0;
        			for(int i=0;i<size;i++) {
        				sum += invokeCounts[i];
        			}
            		if(fuseErrorThreshold>=0 && errorsSum>fuseErrorThreshold) {
        				fuseTime = System.currentTimeMillis();
        				return true;
            		}
            		if(fuseErrorPercentage>=0 && sum>0 && errorsSum*100/sum>fuseErrorPercentage) {
        				fuseTime = System.currentTimeMillis();
        				return true;
            		}

	            } finally {
	            	invokeLock.readLock().unlock();
	            }
        	}
    	}
    	return false;
    }
    


    
    /**
     * 错误调用次数增加
     */
    public void addInvokeErrorCount() {
    	if(fuseCycleTime>0 && fuseCycleTimeNum>0) {
        	long len = fuseCycleTime/fuseCycleTimeNum;
        	if(len==0) {
        		len=1;
        	}
        	int timeNum = (int)((System.currentTimeMillis()-System.currentTimeMillis()%len)/len - fuseErrorCountTime/len);
        	fuseErrorCountTime = (System.currentTimeMillis()-System.currentTimeMillis()%len);
            try {
            	invokeLock.writeLock().lock();
            	int size = invokeErrorCounts.length;
            	if(timeNum>0){
                	for(int i=0;i<size;i++) {
                		if(i+timeNum>=size) {
                			invokeErrorCounts[i] = 0;
                		}else {
                			invokeErrorCounts[i] = invokeErrorCounts[i+timeNum];
                		}
                	}
            	}
            	invokeErrorCounts[size-1]++;
            } finally {
            	invokeLock.writeLock().unlock();
            }
    	}
    }
    
    /**
     * 调用次数增加
     */
    public void addInvokeCount() {
    	if(fuseCycleTime>0 && fuseCycleTimeNum>0) {
        	long len = fuseCycleTime/fuseCycleTimeNum;
        	if(len==0) {
        		len=1;
        	}
        	int timeNum = (int)((System.currentTimeMillis()-System.currentTimeMillis()%len)/len - fuseCountTime/len);
        	fuseCountTime = (System.currentTimeMillis()-System.currentTimeMillis()%len);
            try {
            	invokeLock.writeLock().lock();
            	int size = invokeErrorCounts.length;
            	if(timeNum>0){
                	for(int i=0;i<size;i++) {
                		if(i+timeNum>=size) {
                			invokeCounts[i] = 0;
                		}else {
                			invokeCounts[i] = invokeCounts[i+timeNum];
                		}
                	}
            	}
            	invokeCounts[size-1]++;
            } finally {
            	invokeLock.writeLock().unlock();
            }
    	}
    }
}
